<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>弹幕系统</title>
  <style>
    #container {
      text-align: center;
    }

    #content {
      width: 640px;
      margin: auto;
      position: relative;
      margin-bottom: 10px;
    }

    #canvas {
      position: absolute;
    }

    input {
      /* x轴水平居中 */
      vertical-align: middle;
    }
  </style>
</head>

<body>
  <div id="container">
    <h1>弹幕系统</h1>
    <div id="content">
      <canvas id="canvas"></canvas>
      <!-- controls 播放器控件 -->
      <video id="dm_video" src="./sources/2-17 MyBatis 数据库逆向生成工具-慕课网就业班 2019-11-10 00_50.mp4" width="640" height="380"
        controls></video>
    </div>
    <input type="text" id="text"> <button id="add">添加弹幕</button>
    <input type="color" id="color"> <input id="range" type="range" max="40" min="20">
  </div>
  <script>
    // 弹幕数据
    let data = [
      {
        value: '珠峰架构师公开课',// 值
        speed: 2, // 速度
        time: 0, // 表示出现的时间
        color: 'red', // 颜色
        fontSize: 20// 字体大小
      },
      {
        value: '我是大帅逼',// 值
        speed: 2, // 速度
        time: 0, // 表示出现的时间
        color: 'red', // 颜色
        fontSize: 20// 字体大小
      }
    ]
    // 将document对象this赋值给$符
    let $ = document.querySelector.bind(document)
    let cancas = $('#canvas');
    let video = $('#dm_video');
    // 弹幕类
    class Barrage {
      constructor(obj, ctx) {
        this.value = obj.value; // 弹幕内容
        this.time = obj.time; // 弹幕时间
        this.obj = obj; // 弹幕数据
        this.ctx = ctx; // 弹幕构造器类实例对象
      }
      // 初始化
      init() {
        this.opacity = this.obj.opacity || this.ctx.opacity;
        this.color = this.obj.color || this.ctx.color;
        this.fontSize = this.obj.fontSize || this.ctx.fontSize;
        this.speed = this.obj.speed || this.ctx.speed;

        // 求自己的宽度，目的是用来校验当前是否还需要绘制
        // 创建一个span标签，临时放在document上计算完宽度在删除掉
        let span = document.createElement('span');
        span.innerText = this.value
        span.style.font = this.fontSize + 'px "Microsoft YaHei"';
        span.style.position = 'absolute';
        // 添加到document上
        document.body.appendChild(span);
        // 记录弹幕有多宽
        this.width = span.clientWidth;
        // 拿到宽度再把这个span元素删掉，避免在页面上出现脏数据
        document.body.removeChild(span);
        // 弹幕的出现位置
        this.x = this.ctx.cancas.width;
        this.y = this.ctx.cancas.height * Math.random();
        // y的位置不溢出显现范围
        // 如果y小于了显示文字的高度，那就让他等于文字的高度
        if (this.y < this.fontSize) {
          this.y = this.fontSize;
        }
        // 如果y大于了( cancas高度 - 显示文字的高度 )，那就让他等于= ( cancas高度 - 显示文字的高度 )
        if (this.y > this.ctx.cancas.height - this.fontSize) {
          this.y = this.ctx.cancas.height - this.fontSize
        }
      }


      //渲染自己,将自己画在画布上
      render() {
        this.ctx.context.font = this.fontSize + 'px "Microsoft YaHei"';
        this.ctx.context.fillStyle = this.color;
        this.ctx.context.fillText(this.value, this.x, this.y);
      }
    }
    // 弹幕构造器类
    class CanvasBarrage {
      // 构造器
      constructor(cancas, video, options = {}) {
        if (!cancas || !video) return;
        this.video = video
        this.cancas = cancas
        // 弹幕属性的默认值
        let defaultOptions = {
          speed: 2, // 速度
          time: 0, // 表示出现的时间
          color: 'gold', // 颜色
          fontSize: 20,// 字体大小,
          opacity: 0.3, //透明度
          data: []
        }
        // 对象的合并 将属性全部挂载在实例上，方便使用
        Object.assign(this, defaultOptions, options)
        // 拿到cancas画布
        this.context = cancas.getContext('2d');
        // 设置cancas宽度和高度为视频的宽度
        // clientWidth: 实际宽度、clientHeight: 实际高度
        this.cancas.width = video.clientWidth;
        this.cancas.height = video.clientHeight;
        // 是否暂停
        this.isPaused = true; //默认暂停播放，表示不渲染弹幕
        // 存放所有的弹幕,Barrage 是创造弹幕实例的类
        this.barrages = this.data.map(obj => new Barrage(obj, this))
        // 打印所有的弹幕查看
        console.log(this.barrages);
        // 渲染所有的弹幕
        this.render();
      }
      // 渲染弹幕的方法
      renderBarrage() {
        //将数组中的弹幕一一取出，如果视频时间和弹幕时间相等则渲染弹幕
        let time = this.video.currentTime; // 拿到当前视频的时间
        this.barrages.forEach(barrage => {
          if (!barrage.flag && time >= barrage.time) {
            //开始渲染
            // 先去初始化，初始化后在绘制
            // 1.如果没有初始化先去初始化
            if (!barrage.isInited) {
              barrage.init();
              barrage.isInited = true
            }
            barrage.x -= barrage.speed;
            barrage.render();//渲染自己
            if (barrage.x <= barrage.width * -1) {
              barrage.flag = true;//做停止渲染的标记
            }
          }
        });
      }
      // 渲染弹幕
      render() {
        // 第一次，先进行清空操作，执行渲染弹幕，如果没有暂停继续渲染，相当于递归带调用
        this.context.clearRect(0, 0, this.cancas.width, this.cancas.height);
        // 渲染弹幕的方法
        this.renderBarrage();
        // 不暂停
        if (this.isPaused === false) {
          //requestAnimationFrame相当于一个定时器 ，requestAnimationFrame 是专门为实现高性能的帧动画而设计的一个API
          // 递归渲染
          requestAnimationFrame(this.render.bind(this));
        }
      }
      // 添加弹幕
      add(obj) {
        this.barrages.push(new Barrage(obj, this));
      }
      reset() {
        // 拿到当前视频的时间
        // 先清空内容
        this.context.clearRect(0, 0, this.cancas.width, this.cancas.height);
        this.time = this.video.currentTime;
        this.barrages.forEach(barrage => {
          console.log(barrage);
          barrage.flag = false;
          console.log(barrage.time);
          if (this.time <= barrage.time) {
            // 重新初始化
            barrage.isInited = false;
          } else {
            // 其他弹幕不在渲染
            barrage.flag = true;
          }
        })
      }
    }
    let canvasBarrage
    // 创建socket
    let socket = new WebSocket('ws://localhost:3000');
    // 监听socket 数据
    socket.onopen = function () {
      socket.onmessage = function (msg) {
        let message = JSON.parse(msg.data);
        if (message.type === 'INIT') {
          canvasBarrage = new CanvasBarrage(cancas, video, {
            data: message.data
          })
        } else if (message.type === 'ADD') {
          canvasBarrage.add(message.data)
        }
      }
    }
    // 给播放按钮添加事件
    video.addEventListener('play', function () {
      canvasBarrage.isPaused = false;
      canvasBarrage.render();
    })
    video.addEventListener('pause', function () {
      canvasBarrage.isPaused = true;
    })
    $('#add').addEventListener('click', function () {
      let value = $('#text').value;
      let time = video.currentTime;
      let color = $('#color').value;
      let fontSize = $('#range').value;
      let obj = {
        time,
        value,
        color,
        fontSize
      };
      //添加弹幕 实现添加的功能
      socket.send(JSON.stringify(obj));
      // canvasBarrage.add(obj);
    })
    // 视频拖动事件
    video.addEventListener('seeked', function () {
      // 拖动时间进度条弹重制到当前时间的位置
      canvasBarrage.reset();
    })
  </script>
</body>

</html>